package model.market;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.math.stat.descriptive.moment.Variance;

import model.market.MarketConfig.VolatilityAdjustment;
import model.util.stats.RegressionCalc;
import model.util.stats.RegressionCalc.RegressionResult;


//AMLOW: refactor and comment because we seem to havea few very similar data objects, eg daymarketinfo.
//AMLOW: conver the data sets into objects instead of separate arrays?

//AMLOW: create an average r figure and see if it increases on crash.
//AMLOW: use adjusted r squared? -talk to someone. - no use standard error.
//AMLOW: start checking t-stats and stuff like that?
/**
 * This class contains all market information relevant to a trader's decision.
 */
public class MarketInformation {

	RegressionCalc regress=new RegressionCalc();
	Variance volatility = new Variance();
	private double[] marketPrices;
	
	
	Map<String,BetaAdjuster> betaAdjusters = new HashMap<String, BetaAdjuster>();
	{
		betaAdjusters.put(MarketConfig.BETA_UNADJUSTED, new BetaAdjuster(){
			public double adjust(RegressionResult result) {
				//return rawBeta;
				return result.getBeta();
			}
		});
		betaAdjusters.put(MarketConfig.BETA_BLOOMBERG, new BetaAdjuster(){
			public double adjust(RegressionResult result) {
				return result.getBeta()*.7+.3;
			}
		});
		betaAdjusters.put(MarketConfig.BETA_FIXED, new BetaAdjuster(){
			public double adjust(RegressionResult result) {
				return 1;
			}
		});
		betaAdjusters.put(MarketConfig.BETA_STD_ERR, new BetaAdjuster(){
			public double adjust(RegressionResult result) {
				double rawBeta = result.getBeta();
				double standardError = result.getStandardError();
				double adjusted =rawBeta;
				if(rawBeta>1){
					adjusted-=standardError;
					if(adjusted<1){
						adjusted=1;
					}
				}
				if(rawBeta<1){
					adjusted+=standardError;
					if(adjusted>1){
						adjusted=1;
					}
				}
				return adjusted;
			}
		});
	}
	
	
	/**
	 * index is cap weighted and price based, i.e not performance based.
	 * because all the assets start from the same position, this means that the index is really
	 * equally weighted, because as a price increases, so does market cap, and therefore the weighting in
	 * the index.
	 * 
	 * however index returns are price changes plus asset cashflows.
	 */
	List<Double> indexReturnsInclCashAndCapitalLessRiskFree= new ArrayList<Double>();
	private List<Double> indexReturnsExclCashInclCapital= new ArrayList<Double>();
	private List<Double> indexVariance = new ArrayList<Double>();
	private List<Double> indexVolume = new ArrayList<Double>();
	
	private List<Double> indexCashflows = new ArrayList<Double>();
	private List<Double> indexDivYields = new ArrayList<Double>();
	private List<Double> equityRiskPremiums = new ArrayList<Double>();
	private List<Double> averageRsq = new ArrayList<Double>();
	
	/**
	 * pure index prices (i.e no dividends are included in calculation).
	 */
	private List<Double> indexPrices= new ArrayList<Double>();
	
	private List<Double>[] assetLiquidity;
	
	/**
	 * 5 assets are available to choose from..
	 * asset returns are the dividend cashflows, plus the capital returns/ over the intial capital.
	 */
	//CAPM uses this..
	List<Double>[] assetReturnsInclCashAndCapitalLessRiskFree;
	private List<RegressionResult>[] assetCapmResults;
	
	
	//momentum uses this..
	private List<Double>[] assetReturnsExclCashInclCapital;
	
	/**
	 * volumes are measured as market value.
	 */
	private List<Double>[] assetVolume;
	
	private List<Double>[] assetPrices;
	private List<Double>[] assetVariance;
	
	/**
	 * measured as cash.
	 */
	private List<Double>[] assetCashflows;
	private List<Double>[] assetDivYield;
	
	private List<Double> riskFreeReturns= new ArrayList<Double>();
	
	private int numberOfAssets;
	MarketConfig config;
	
	public int getPeriodsInYear() {
		return config.getPeriodsInYear();
	}
	
	public List<Double> getEquityRiskPremiums() {
		return equityRiskPremiums;
	}
	
	public int getFirstTradePeriod(){
		return config.getTradeDelay();
	}
	
	public MarketConfig getConfig() {
		return config;
	}
	
	@SuppressWarnings("unchecked")
	public MarketInformation(MarketConfig config){
		
		this.numberOfAssets=config.getNumberOfAssets();
		this.config=config;
		
		assetReturnsInclCashAndCapitalLessRiskFree=new List[numberOfAssets];
		assetReturnsExclCashInclCapital = new List[numberOfAssets];
		assetCapmResults = new List[numberOfAssets];
		assetPrices = new List[numberOfAssets];
		assetVariance = new List[numberOfAssets];
		assetCashflows = new List[numberOfAssets];
		assetDivYield= new List[numberOfAssets];
		assetVolume = new List[numberOfAssets];
		assetLiquidity= new List[numberOfAssets];
		marketPrices=new double[numberOfAssets];
		
		//Initialise the lists..
		for(int i=0;i<numberOfAssets;i++){
			assetReturnsInclCashAndCapitalLessRiskFree[i]=new ArrayList<Double>();
			assetReturnsExclCashInclCapital[i]=new ArrayList<Double>();
			assetCapmResults[i]= new ArrayList<RegressionResult>();
			assetPrices[i]=new ArrayList<Double>();
			assetCashflows[i]=new ArrayList<Double>();
			assetDivYield[i]=new ArrayList<Double>();
			assetVariance[i]=new ArrayList<Double>();
			assetVolume[i]=new ArrayList<Double>();
			assetLiquidity[i]= new ArrayList<Double>();
		}
	}
	
	
	/**
	 * 
	 * @param todayMarketPrices
	 * @param todayAssetCashFlows
	 * @param todayAssetVolumes
	 * @param rfReturn - annualised figure.
	 */
	public void endOfDay(double[] todayMarketPrices,  
							double[] todayAssetCashFlows,
							long[] todayAssetVolumes,
							double rfReturn){
		double indexPrice = 0;
		double volumeSum=0;
		double cashSum=0;
		
		for(int i=0;i<todayMarketPrices.length;i++){
			double yesterdayAssetPrice = assetPrices[i].size()==0?todayMarketPrices[i]:assetPrices[i].get(assetPrices[i].size()-1);
			
			assetPrices[i].add(todayMarketPrices[i]);
			marketPrices[i]=todayMarketPrices[i];
			assetCashflows[i].add(todayAssetCashFlows[i]);
			assetDivYield[i].add(todayAssetCashFlows[i]/yesterdayAssetPrice);
			
			//AM: here we convert volume to a value figure..
			//AMXX: what was going on.. weve got volumes as number traded, volume as market value.. jesus!
			double volumeValue = todayAssetVolumes[i]*todayMarketPrices[i];
			assetVolume[i].add(volumeValue);
			
			double capitalReturn = (todayMarketPrices[i]-yesterdayAssetPrice)/yesterdayAssetPrice;
			
			assetLiquidity[i].add(Math.abs(capitalReturn)<0.001?
											//AM: a big number if can trade without move.
											//AMLOW: sort these figures a bit better..
											(todayAssetVolumes[i]==0?-1:volumeValue/.001):
											(volumeValue)/Math.abs(capitalReturn));
			double cashflowReturn = (todayAssetCashFlows[i]/yesterdayAssetPrice);
			assetReturnsInclCashAndCapitalLessRiskFree[i].add(capitalReturn+cashflowReturn-(rfReturn/config.getPeriodsInYear()));
			assetReturnsExclCashInclCapital[i].add(capitalReturn);
			
			//AMLOW:  market cap proportional to price, but should really make this stuff more sophisticated..
			indexPrice+=todayMarketPrices[i];
			volumeSum+=volumeValue;
			cashSum+=todayAssetCashFlows[i];
			
		}
		
		double yesterdayIndexPrice = indexPrices.size()==0?indexPrice:indexPrices.get(indexPrices.size()-1);
		
		double indexCashflowReturn=cashSum/yesterdayIndexPrice;
		double indexCapitalReturn = (indexPrice-yesterdayIndexPrice)/yesterdayIndexPrice;
		double indexReturnInclCashAndCapital=indexCashflowReturn+indexCapitalReturn;
		double indexReturnExclCashInclCapital =indexCapitalReturn;
		
		indexCashflows.add(cashSum);
		indexDivYields.add(indexCashflowReturn);
		//System.out.println("Index price is "+indexPrice);
		indexPrices.add(indexPrice);
		indexReturnsExclCashInclCapital.add(indexReturnExclCashInclCapital);
		indexReturnsInclCashAndCapitalLessRiskFree.add(indexReturnInclCashAndCapital-(rfReturn/config.getPeriodsInYear()));
		
		indexVolume.add(volumeSum);
		
		riskFreeReturns.add(rfReturn);
		
		List<Double> indexParam = indexReturnsInclCashAndCapitalLessRiskFree;
		//AM: run regression over say last 10 or 50 periods only..
		int size = indexParam.size();
		int preferredSample = config.getTradeDelay();
		if(size>preferredSample){
			indexParam =indexParam.subList(size-preferredSample, size-1);
		}
		double averageRsq =0;
		for(int i=0;i<numberOfAssets;i++){
			
			List<Double> assetParam = assetReturnsInclCashAndCapitalLessRiskFree[i];
			if(size>preferredSample){
				assetParam = assetParam.subList(size-preferredSample, size-1);
			}
			
			//AM: capm calcs..
			RegressionResult regression = runCapm(assetReturnsInclCashAndCapitalLessRiskFree[i].size(),assetParam, indexParam);
			assetCapmResults[i].add(regression);
			
			//AMLOW: variance may need adjusting or otherwise because it can change rapidly and causes 
			//funny output because sharpe ratio swings wildly. - how about having a 'remove outliers' option?
			// maybe an adjustment similar to beta?
			//AM: Variance adjustment is not properly supported.. option not available at the moment in test harness.
			//AM: note that variance adjustment is just at asset level, not index.
			double var = runVariance(assetParam)*config.getPeriodsInYear();
//AMLOW: delete this code.. adjustment picked up in std dev calc.		switch(config.getVolatilityAdjustment()){
//				case FIXED:
//					if(i==0){
//						var=config.getVolatilityAsset0()*config.getVolatilityAsset0();
//					}else{
//						var = config.getVolatility()*config.getVolatility();
//					}
//				case ADJUSTED:
//					if(i==0){
//						var=(config.getVolatilityAsset0()*config.getVolatilityAsset0()+var)/2;
//					}else{
//						var = (config.getVolatility()*config.getVolatility()+var)/2;
//					}
//					
//			}
			
			assetVariance[i].add(var);
			averageRsq+=regression.getRsquared();
		}
		double indexVol = runVariance(indexParam)*config.getPeriodsInYear();
		
		
		indexVariance.add(indexVol);
		this.averageRsq.add(averageRsq/getNumberOfAssets());
		
		equityRiskPremiums.add(calculateEquityRiskPremium());
	}


	public double runVariance(List<Double> data) {
		int preferredSample = config.getTradeDelay();
		int size=data.size();
		if(size>preferredSample){
			data=data.subList(size-preferredSample, size-1);
		}
		
		//We have to make a bloody double[]..
		double[] varianceParam = new double[data.size()];
		for(int periodCount= 0; periodCount<varianceParam.length;periodCount++){
			varianceParam[periodCount]= data.get(periodCount);
		}
		double assetVol = volatility.evaluate(varianceParam);
		return assetVol;
	}
	
	public RegressionResult runCapm(int latestPeriod,List<Double> assetReturnsInclCashAndCapitalLessRiskFree,List<Double> indexReturnsInclCashAndCapitalLessRiskFree){
		
		int periodsPerSampleForRegression = getConfig().getPeriodsPerSampleForRegression();
		
		List<Double> assetReturnsFiltered =new ArrayList<Double>();
		List<Double> indexReturnsFiltered = new ArrayList<Double>();
		
		//AMLOW: dirty little hack.. we just add the returns together. shoudl fix (i.e calculate and use proper return figures over longer periods).
		double assetReturn =0;
		double indexReturn =0;
		int period= latestPeriod-assetReturnsInclCashAndCapitalLessRiskFree.size();
		for(int i=0;i<assetReturnsInclCashAndCapitalLessRiskFree.size();i++){
			assetReturn+=assetReturnsInclCashAndCapitalLessRiskFree.get(i);//assetReturnsInclCashAndCapitalLessRiskFree.size()-1-i);
			indexReturn+=indexReturnsInclCashAndCapitalLessRiskFree.get(i);//indexReturnsInclCashAndCapitalLessRiskFree.size()-1-i);
			if(period%periodsPerSampleForRegression==0){
				//AM: work backwards so we know we use the latest..
				assetReturnsFiltered.add(assetReturn);
				indexReturnsFiltered.add(indexReturn);
				assetReturn=0;
				indexReturn=0;
			}
			period++;
		}
		if(assetReturn!=0){
			assetReturnsFiltered.add(assetReturn);
			indexReturnsFiltered.add(indexReturn);
		}
		
		//AM: capm calcs..
		RegressionResult regression = regress.run(assetReturnsFiltered, indexReturnsFiltered);
		regression.setBeta(betaAdjusters.get(config.getBetaCalculation()).adjust(regression));
		return regression;
	}



	public List<Double> getIndexReturnsInclCashAndCapitalLessRiskFree() {
		return indexReturnsInclCashAndCapitalLessRiskFree;
	}


	public List<Double> getIndexReturnsExclCashInclCapital() {
		return indexReturnsExclCashInclCapital;
	}


	public List<Double> getIndexVariance() {
		return indexVariance;
	}


	public List<Double> getIndexVolume() {
		return indexVolume;
	}


	public List<Double> getIndexCashflows() {
		return indexCashflows;
	}

	public List<Double> getIndexDivYields() {
		return indexDivYields;
	}
	
	public List<Double> getIndexPrices() {
		return indexPrices;
	}


	public List<Double>[] getAssetLiquidity() {
		return assetLiquidity;
	}


	public List<Double>[] getAssetReturnsInclCashAndCapitalLessRiskFree() {
		return assetReturnsInclCashAndCapitalLessRiskFree;
	}


	public List<RegressionResult>[] getAssetCapmResults() {
		return assetCapmResults;
	}


	public List<Double>[] getAssetReturnsExclCashInclCapital() {
		return assetReturnsExclCashInclCapital;
	}


	public List<Double>[] getAssetVolume() {
		return assetVolume;
	}


	public List<Double>[] getAssetPrices() {
		return assetPrices;
	}


	public List<Double>[] getAssetVariance() {
		return assetVariance;
	}


	/**
	 * 
	 * @return - period dividend streams.. note not annualised.
	 */
	public List<Double>[] getAssetCashflows() {
		return assetCashflows;
	}

	/**
	 * AM: note this is not annualised.. per period yield.
	 * @return
	 */
	public List<Double>[] getAssetDivYield() {
		return assetDivYield;
	}

	public List<Double> getRiskFreeReturns() {
		return riskFreeReturns;
	}


	public int getNumberOfAssets() {
		return numberOfAssets;
	}

	public List<Double> getAverageRsq() {
		return averageRsq;
	}


	public double getBeta(int asset) {
		List<RegressionResult> list = getAssetCapmResults()[asset];
		return list.get(list.size()-1).getBeta();
		
	}


	public double getEquityRiskPremium() {
		return equityRiskPremiums.get(equityRiskPremiums.size()-1);
	}
	
	private double calculateEquityRiskPremium(){
		if(config.getErpCalculation().equals(MarketConfig.ERP_FIXED)){
			return config.getShareCashflowRate()-config.getInitialRiskFreeRate();
		}else{
			//observed..
			List<Double> list = getIndexReturnsInclCashAndCapitalLessRiskFree();
			double totalPremium = 0;
			for(double excessReturn:list){
				totalPremium+=excessReturn;
			}
			double ret=totalPremium/list.size()*config.getPeriodsInYear();
			if(ret<0){
				ret=0;
			}
			//AMLOW: we just multiplying to make a year in a lot of places we should be doing something more clever.
			
			return ret;
		}
		
	}


	public double getAssetNonSpecificVariance(int i) {
		List<RegressionResult> capmInfo = getAssetCapmResults()[i];
		RegressionResult latest = capmInfo.get(capmInfo.size()-1);
		List<Double> variances = getAssetVariance()[i];
		Double totalAssetVariance = variances.get(variances.size()-1);
		Double marketVariance = getIndexVariance().get(getIndexVariance().size()-1);
		return getAssetNonSpecificVariance(latest.getBeta(), totalAssetVariance, marketVariance);
		
	}
	
	
	public double getAssetNonSpecificVariance(double beta,double totalAssetVariance,double marketVariance){
		
		double assetCorrelationWithMarket =isZero(totalAssetVariance)?0: 
			beta*Math.sqrt(marketVariance)/Math.sqrt(totalAssetVariance);
		return Math.abs(assetCorrelationWithMarket*totalAssetVariance);
		
	}
	
	private boolean isZero(double number) {
		//AMLOW: : dodgy, fix..
		return Double.isNaN(number)||(number<.0000001&&number>-.0000001);
	}

	public boolean valueShock() {
		double asset0Shock = config.getAsset0Shock();
		return asset0Shock!=0;
	}
	
	public double[] getAssetBetas(){
		double[] ret = new double[getConfig().getNumberOfAssets()];
		
		for(int assetRef=0;assetRef<getConfig().getNumberOfAssets();assetRef++){
			if(this.assetCapmResults[assetRef].size()==0){
				return ret;
			}
			ret[assetRef]=assetCapmResults[assetRef].get(assetCapmResults[assetRef].size()).getBeta();
		}
		return ret;	
	}

	public double[] getMarketPrices() {
		
		
		
		return marketPrices;
	}

}
